//
// This is example code from Chapter 9.8 "The Date class" of 
// "Programming -- Principles and Practice Using C++" by Bjarne Stroustrup
//

#include "Chrono.h"

namespace Chrono {

// member function definitions:

//------------------------------------------------------------------------------

Date::Date(int yy, Month mm, int dd)
    : y(yy), m(mm), d(dd)
{
    if (!is_date(yy,mm,dd)) throw Invalid();
}

//------------------------------------------------------------------------------

const Date& default_date()
{
    static const Date dd(2001,Date::jan,1); // start of 21st century
    return dd;
}

//------------------------------------------------------------------------------

Date::Date()
    :y(default_date().year()),
     m(default_date().month()),
     d(default_date().day())
{
}

//------------------------------------------------------------------------------


int Date::day_in_month() const
{
	int result;
	switch (m)
	{
	case Date::apr: case Date::jun: case Date::sep: case Date::nov:
		result = 30;
		break;
	case Date::feb:
		{
			if (leapyear(y))
				result  = 29;
			else
				result  = 28;
			break;
		}
	default:
		result   = 31;
	}

	return result;
}



void Date:: add_day(int n)
{
	int dim = day_in_month();
	for (int i=0; i < n; ++i)
	{
		d+=1;
		if (d > day_in_month())
		{
			d = 1;
			add_month(1);
			dim = day_in_month();
		}
	}	
}

//------------------------------------------------------------------------------

void Date::add_month(int n)
{
    // ...
	for (int i=0; i < n;++i)
	{
		if (m==dec){m=jan; add_year(1);}
		else
			m=Month(m+1);
	}

	if ((!leapyear(y))&&m==feb&&d==29)
	{
		m=mar;
		d=1;
	}
}

//------------------------------------------------------------------------------

void Date::add_year(int n)
{
    if (m==feb && d==29 && !leapyear(y+n)) { // beware of leap years!
        m = mar;        // use March 1 instead of February 29
        d = 1;
    }
    y+=n;
}

//------------------------------------------------------------------------------

// helper functions:

bool is_date(int y, Date::Month  m, int d)
{
    // assume that y is valid

    if (d<=0) return false;            // d must be positive

    int days_in_month = 31;            // most months have 31 days

    switch (m) {
case Date::feb:                        // the length of February varies
    days_in_month = (leapyear(y))?29:28;
    break;
case Date::apr: case Date::jun: case Date::sep: case Date::nov:
    days_in_month = 30;                // the rest have 30 days
    break;
    }

    if (days_in_month<d) return false;

    return true;
} 

//------------------------------------------------------------------------------

bool leapyear(int y)
{
    // See exercise ???
    if ((y%4==0&&y%100!=0)||y%400==0)
		return true;
	return false;
}

//------------------------------------------------------------------------------

bool operator==(const Date& a, const Date& b)
{
    return a.year()==b.year()
        && a.month()==b.month()
        && a.day()==b.day();
}

//------------------------------------------------------------------------------

bool operator!=(const Date& a, const Date& b)
{
    return !(a==b);
}

//------------------------------------------------------------------------------

ostream& operator<<(ostream& os, const Date& d)
{
    return os << '(' << d.year()
              << ',' << d.month()
              << ',' << d.day() 
              << ')';
}

//------------------------------------------------------------------------------

istream& operator>>(istream& is, Date& dd)
{
    int y, m, d;
    char ch1, ch2, ch3, ch4;
    is >> ch1 >> y >> ch2 >> m >> ch3 >> d >> ch4;
    if (!is) return is;
    if (ch1!='(' || ch2!=',' || ch3!=',' || ch4!=')') { // oops: format error
        is.clear(ios_base::failbit);                    // set the fail bit
        return is;
    }
    dd = Date(y,Date::Month(m),d);     // update dd
    return is;
}

//------------------------------------------------------------------------------

enum Day {
    monday, tuesday, wednesday, thursday, friday, saturday,  sunday
};

//------------------------------------------------------------------------------

Day day_of_week(const Date& d)
{
   	int dd = d.day();
	Date::Month mm = d.month();
	int yy = d.year();
	if (mm <= Date::feb){dd+=3;yy-=1;}
	int w =  (dd + yy + yy/4 - yy/100 + yy/400 + (31*mm + 10)/12) % 7;
	return Day(w);
}

//------------------------------------------------------------------------------

Date next_sunday(const Date& d)
{
    // ...
	Date tmp = d;
	if (day_of_week(tmp)==sunday) 
		tmp.add_day(7);
	else
		while(day_of_week(tmp)!=sunday)
			tmp.add_day(1);
	return tmp;
}

//------------------------------------------------------------------------------

Date next_weekday(const Date& d)
{
    // ...
    Date tmp = d;
	while(tmp.day()!=sunday&&tmp.day()!=saturday)
		tmp.add_day(1);
	return tmp;
}

//------------------------------------------------------------------------------

} // Chrono

//------------------------------------------------------------------------------

